package com.dospyer.jsql;

import com.dospyer.jsql.trx.TransactionManager;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

/**
 * @author : peigen
 * Date: 2021/12/9 8:14 下午
 */
@Slf4j
public class TransactionDemo {

    /**
     * CREATE TABLE `t` (
     * `id` int(11) NOT NULL,
     * `k` varchar(255) DEFAULT NULL,
     * PRIMARY KEY (`id`)
     * ) ENGINE=InnoDB;
     * <p>
     * insert into t(id, k) values(1,'1');
     * <p>
     * | 事务A                                           | 事务B                                                        |
     * | ---------------------------------------------- | ------------------------------------------------------------ |
     * | begin;
     * | select k form t where id = 1;                  | begin;                                                       |
     * |                                                | select k form t where id = 1;                                |
     * |                                                | update t set k='2' where id = 1;                               |
     * | select k form where id = 1;<br/>查询得到结果A1   |                                                              |
     * |                                                | commit；                                                     |
     * | select k form where id = 1;<br/>查询得到结果A2   |                                                              |
     * | commit;                                        |                                                              |
     * | select k form where id = 1;<br/>查询得到结果A3   |                                                              |
     * <p>
     * 不同的隔离级别查询出来的 A1、A2、A3分别是
     */

    @BeforeAll
    public static void init() {
        TransactionManager trxManager = TransactionManager.getInstance();
        // 开启事务
        trxManager.begin();

        Row row = new Row();
        row.setId(1L);
        row.setContent("1");
        BufferPool.getInstance().insert(row);

        // C闭事务
        trxManager.commit();
    }

    @Test
    public void testA() throws InterruptedException {
        Thread threadA = new Thread(() -> {
            // 查询之前先开启事务
            TransactionManager trxManager = TransactionManager.getInstance();
            trxManager.begin();

            // 生成一致性视图
            trxManager.assignReadView();

            Row record = BufferPool.getInstance().getByIdWithMvcc(1L);
            log.info("事务 A 查询：首次查询 k = {}", record.getContent());

            // 等待 B 线程执行
            execOther();

            record = BufferPool.getInstance().getByIdWithMvcc(1L);
            log.info("事务 A 查询：事务 B 更新数据后，k = {}", record.getContent());

            // 等待 B 线程执行
            execOther();

            record = BufferPool.getInstance().getByIdWithMvcc(1L);
            log.info("事务 A 查询：事务 B 提交后， k = {}", record.getContent());

            // 提交事务
            trxManager.commit();

            // 重新生成一致性视图
            trxManager.begin();
            trxManager.assignReadView();
            record = BufferPool.getInstance().getByIdWithMvcc(1L);
            log.info("事务 A 查询：事务 A 提交后再次查询 k = {}", record.getContent());
            trxManager.commit();
            log.info("事务 A 执行完毕");
        });

        Thread threadB = new Thread(() -> {
            // 查询之前先开启事务
            TransactionManager trxManager = TransactionManager.getInstance();
            trxManager.begin();

            // 生成一致性视图
            trxManager.assignReadView();

            // 等待 A 线程执行
            execOther();

            Row record = BufferPool.getInstance().getByIdWithMvcc(1L);
            log.info("事务 B 首次查询， k = {}", record.getContent());

            Row newRow = new Row();
            newRow.setId(1L);
            newRow.setContent("2");
            BufferPool.getInstance().updateById(newRow);
            log.info("事务 B 完成更新数据 k = {}", record.getContent());

            // 等待 A 线程执行
            execOther();

            // 提交事务
            trxManager.commit();
            log.info("事务 B 完成事务提交");

            // 唤醒线程 A
            notifyOther();
            log.info("事务 B 执行完毕");
        });
        threadA.start();
        threadB.start();
        threadA.join();
        threadB.join();
    }

    private synchronized void execOther() {
        try {
            notifyAll();
            wait();
        } catch (InterruptedException e) {
            log.error("中断异常", e);
        }
    }

    /**
     * 唤醒其他线程
     */
    private synchronized void notifyOther() {
        notifyAll();
    }
}
